package lumis.service.portalmanagement.systeminfo;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.text.DateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Queue;
import java.util.SortedMap;
import java.util.TimeZone;
import java.util.TreeMap;
import java.util.Map.Entry;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;

import lumis.portal.PortalConfiguration;
import lumis.portal.PortalContext;
import lumis.portal.PortalException;
import lumis.portal.dao.DaoException;
import lumis.portal.dao.jdbc.ITransactionJdbc;
import lumis.portal.transaction.PortalTransactionFactory;
import lumis.util.ITransaction;
import lumis.util.log.ILogger;
import lumis.util.log.LoggerFactory;
/**
 * Helper class that gets all the information shown by System Info service.
 * 
 * @version $Revision: 11804 $ $Date: 2010-07-29 10:06:21 -0300 (Thu, 29 Jul 2010) $
 * @since 5.6.0
 */
public class DataHelper 
{
	private static ILogger logger = LoggerFactory.getLogger(DataHelper.class);
	private static final String NEW_LINE = "\r\n";
	
	/**
	 * Builds a map with the database information.
	 * @return	a map containing the database information.
	 * @since 5.6.0
	 */
	public static Map<String, String> obtainDatabaseInformation() 
	{	
		Map<String, String> databaseInfoMap = new HashMap<String, String>();
		
		// create transaction object
		ITransaction transaction = PortalTransactionFactory.createTransaction(); 
		try
		{
			transaction.begin();			
			
			// execute transactional operations 
			ITransactionJdbc daoTransactionJdbc = (ITransactionJdbc) transaction;

			Connection connection = daoTransactionJdbc.getConnection();
			DatabaseMetaData databaseMetaData = connection.getMetaData();
						
			databaseInfoMap.put("database.product.name", databaseMetaData.getDatabaseProductName());
			databaseInfoMap.put("database.product.version", databaseMetaData.getDatabaseProductVersion());
			databaseInfoMap.put("database.driver.name", databaseMetaData.getDriverName());
			databaseInfoMap.put("database.driver.version", databaseMetaData.getDriverVersion());
			 			
 			transaction.commit();
		} 
		catch (Exception e) 
		{
			logger.error("Exception obtaining database information", e);
		} 
		finally 
		{
			try 
			{
				// automatically rolls back if commit was not executed
				transaction.dispose(); 
			} 
			catch (DaoException e) 
			{
				logger.error("Exception obtaining database information", e);
			}
		}
		
		return databaseInfoMap;
	}
	
	/**
	 * Builds a map with the environment variables.
	 * @return	a map containing the environment variables.
	 * @since 5.6.0
	 */
	public static Map<String, String> obtainEnvironmentData() 
	{
		SortedMap<String, String> envMap = new TreeMap<String, String>();
		for (Entry<String, String> e: System.getenv().entrySet())
		{
			envMap.put(e.getKey(), e.getValue());
		}		
		return envMap;
	}
	
	/**
	 * Builds a map with general information like date, user timezone, user locale, etc.
	 * @return	a map containing general information.
	 * @since 5.6.0
	 */
	public static Map<String, String> obtainGeneralInformation() 
	{
		Date date = new Date();
		
		DateFormat dateFormat = DateFormat.getDateTimeInstance();
		String dateString = dateFormat.format(date);
				
		Map<String, String> generalInfoMap = new HashMap<String, String>();
		generalInfoMap.put("stringcurrentdate", dateString);		
		generalInfoMap.put("timezone", TimeZone.getDefault().toString());		
		generalInfoMap.put("locale", Locale.getDefault().toString());
		
		return generalInfoMap;
	}

	/**
	 * Builds a map with memory use information.
	 * @return	a map containing the memory use information.
	 * @since 5.6.0
	 */
	public static Map<String, String> obtainMemoryUseInformation() 
	{
		Map<String, String> memoryUseInfoMap = new HashMap<String, String>();
		
		long freeMemory = Runtime.getRuntime().freeMemory() / 1024;
		long totalMemory = Runtime.getRuntime().totalMemory() / 1024;
		long maxMemory = Runtime.getRuntime().maxMemory() / 1024;
		long memoryInUse = totalMemory - freeMemory;
		
		memoryUseInfoMap.put("freeMemory", freeMemory + " KB");
		memoryUseInfoMap.put("totalMemory", totalMemory + " KB");
		memoryUseInfoMap.put("memoryInUse", memoryInUse + " KB");
		memoryUseInfoMap.put("maxMemory", maxMemory + " KB");
		
		return memoryUseInfoMap;		
	}
	
	/**
	 * Builds a map with the page cache information.
	 * @return	a map containing the page cache information.
	 * @since 5.6.0
	 */
	public static Map<String, String> obtainPageCacheInformation() 
	{
		Map<String, String> pageCacheInfoMap = new HashMap<String, String>();		
		
		try 
		{
			Class<?> pageCacheCleanerClass = Class.forName("lumis.portal.page.cache.PageCacheCleaner");
			
			// get the queue
			Field eventsQueue = pageCacheCleanerClass.getDeclaredField("eventsQueue");			 
			eventsQueue.setAccessible(true);
			Queue<?> q = (Queue<?>) eventsQueue.get(null);			
					
			pageCacheInfoMap.put("queuesize", "" + q.size());
		} 
		catch (Exception e) 
		{
			logger.error("Exception obtaining page cache information", e);
		} 
		
		return pageCacheInfoMap;		
	}
	
	/**
	 * Builds a map with the portal information.
	 * @return	a map containing the portal information.
	 * @since 5.6.0
	 */
	public static Map<String, String> obtainPortalInformation() 
	{		
		Map<String, String> portalInfoMap = new HashMap<String, String>();

		portalInfoMap.put("lumis.portal.lumisdatapath", PortalConfiguration.getCurrentConfiguration().getLumisDataPath());
		portalInfoMap.put("lumis.portal.webroot", PortalContext.getRealPath("/"));
		
		try 
		{			
			portalInfoMap.put("lumis.portal.version", PortalContext.getBuildConfig().toString());			
		} 
		catch (PortalException e) 
		{
			logger.error("Exception obtaining portal information", e);
		}
		
		try 
		{
			DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
			DocumentBuilder db = dbf.newDocumentBuilder();						
			portalInfoMap.put("lumis.portal.xml.dom.parser", db.getClass().getName());
		} 
		catch (ParserConfigurationException e) 
		{
			logger.error("Exception obtaining portal information", e);
		}
		
		try 
		{
			SAXParserFactory fac = SAXParserFactory.newInstance();
			SAXParser parser = fac.newSAXParser();
			portalInfoMap.put("lumis.portal.xml.sax.parser", parser.getClass().getName());
		} 
		catch (Exception e) 
		{
			logger.error("Exception obtaining portal information", e);
		} 
		
		try 
		{
			TransformerFactory tf = TransformerFactory.newInstance();
			portalInfoMap.put("lumis.portal.xml.xsl.transformer", tf.getClass().getName());
		}
		catch (TransformerFactoryConfigurationError e) 
		{
			logger.error("Exception obtaining portal information", e);
		}
		
		return portalInfoMap;
	}
	
	/**
	 * Builds a map with the system properties.
	 * @return	a map containing the system properties.
	 * @since 5.6.0
	 */
	public static Map<String, String> obtainSystemProperties() 
	{
		SortedMap<String, String> spMap = new TreeMap<String, String>();		
		
		for (Entry<?, ?> e: System.getProperties().entrySet())
		{
			spMap.put((String) e.getKey(), (String) e.getValue());
		}
		
		return spMap;
	}

	/**
	 * Builds a map with the thread information.
	 * @return	a map containing the thread information.
	 * @since 5.6.0
	 */
	public static Map<String, String> obtainThreadInformation() 
	{
		SortedMap<String, String> threadInfoMap = new TreeMap<String, String>();
		
		for (Thread thread : Thread.getAllStackTraces().keySet()) 
		{						
			StringBuilder sb = new StringBuilder();
			for (StackTraceElement element : thread.getStackTrace()) 
			{
				sb.append(element + NEW_LINE);
			}
			threadInfoMap.put(thread.getName(), sb.toString());
		}
		return threadInfoMap;
	}
	
	/**
	 * Builds a string with all the information (database, environment, general info, etc).
	 * @return a string representation of all information.
	 * @since 5.6.0
	 */
	public static String obtainAllInformationAsString() 
	{
		Map<String, String> databaseInformation = obtainDatabaseInformation();
		Map<String, String> environmentData = DataHelper.obtainEnvironmentData();
		Map<String, String> gereralInformation = DataHelper.obtainGeneralInformation();
		Map<String, String> memoryUseInformation = DataHelper.obtainMemoryUseInformation();		
		Map<String, String> pageCacheInformation = DataHelper.obtainPageCacheInformation();
		Map<String, String> portalInformation = DataHelper.obtainPortalInformation();
		Map<?, ?> systemProperties = obtainSystemProperties();
		Map<String, String> threadInformation = obtainThreadInformation();
		
		StringBuilder sb = new StringBuilder();

		appendDataInformation(sb, "General Information", gereralInformation);
		sb.append(NEW_LINE);
		appendDataInformation(sb, "System Properties", systemProperties);
		sb.append(NEW_LINE);		
		appendDataInformation(sb, "Environment Variables", environmentData);
		sb.append(NEW_LINE);		
		appendDataInformation(sb, "Database Information", databaseInformation);
		sb.append(NEW_LINE);
		appendDataInformation(sb, "Portal Information", portalInformation);
		sb.append(NEW_LINE);
		appendDataInformation(sb, "Page Cache Information", pageCacheInformation);
		sb.append(NEW_LINE);
		appendDataInformation(sb, "Thread Information", threadInformation);
		sb.append(NEW_LINE);		
		appendDataInformation(sb, "Memory Use Information", memoryUseInformation);
		
		return sb.toString();
	}
	
	/**
	 * Appends the information in the map as a string in the string builder.
	 * @param sb			the instance of StringBuilder in which the map will be appended.
	 * @param sourceTitle	a title used to the information inside the map.
	 * @param dataMap		the map containing the information.
	 * @since 5.6.0
	 */
	public static void appendDataInformation(StringBuilder sb, 
											String sourceTitle, 
											Map<?, ?> dataMap) 
	{
		sb.append("-------------------------------------------------------" + NEW_LINE);
		sb.append(sourceTitle + NEW_LINE);
		sb.append("-------------------------------------------------------" + NEW_LINE);
		
		for (Object key : dataMap.keySet()) {
			sb.append(key + ": " + dataMap.get(key) + NEW_LINE);
		}		
	}
}
